package drds.plus.datanode.select.equity_manager_select;

import drds.plus.common.utils.WeightHolder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * <pre>
 * 使用例子： RandomWeightSelect weightRandom = new RandomWeightSelect( {key1=>8, key2=9 ,key3=10}); weightRandom.query(excludeKeys);
 */
@Slf4j
public class RandomWeightSelect {

    public static final int DEFAULT_WEIGHT_INIT = 10;
    @Setter
    @Getter
    private final WeightHolder<Weight> weightHolder = new WeightHolder<Weight>();
    /**
     * 假设三个库权重 10 9 8 那么areaEnds就是 10 19 27 随机数是0~27之间的一个数 分别去上面areaEnds里的元素比。
     * 发现随机数小于一个元素了，则表示应该选择这个元素 注意：该方法不能改变参数数组内容
     */
    private final Random random = new Random();
    @Setter
    @Getter
    private Map<String, Integer> weightConfigMap;

    public RandomWeightSelect(Map<String, Integer> weightConfigs) {
        this.init(weightConfigs);
    }

    public RandomWeightSelect(String[] keys) {
        Map<String, Integer> weightConfigMap = new HashMap<String, Integer>(keys.length);
        for (String key : keys) {
            weightConfigMap.put(key, DEFAULT_WEIGHT_INIT);
        }
        this.init(weightConfigMap);
    }

    /**
     * 支持动态修改
     */
    public void setWeightConfigMap(Map<String, Integer> weightConfig) {
        this.init(weightConfig);
    }

    private void init(Map<String, Integer> weightConfigMap) {
        this.weightConfigMap = weightConfigMap;
        //
        String[] weightKeys = weightConfigMap.keySet().toArray(new String[0]);
        int[] weightValues = new int[weightConfigMap.size()];
        for (int i = 0; i < weightValues.length; i++) {
            weightValues[i] = weightConfigMap.get(weightKeys[i]);
        }
        int[] weightValueEnds = genWeightValueEnds(weightValues);
        weightHolder.set(new Weight(weightKeys, weightValues, weightValueEnds));
    }

    /**
     * 权重累计末尾点
     */
    private static int[] genWeightValueEnds(int[] weightValues) {
        if (weightValues == null) {
            return null;
        }
        int[] weightValueEnds = new int[weightValues.length];
        int sum = 0;
        for (int i = 0; i < weightValues.length; i++) {
            sum += weightValues[i];
            weightValueEnds[i] = sum;
        }
        if (log.isDebugEnabled()) {
            log.debug("generate " + Arrays.toString(weightValueEnds) + " from " + Arrays.toString(weightValues));
        }
        if (sum == 0) {
            log.warn("generate " + Arrays.toString(weightValueEnds) + " from " + Arrays.toString(weightValues));
        }
        return weightValueEnds;
    }


    /**
     * 根据权重获取随机后的key
     */
    public String select() {
        return select(new ArrayList<String>(1));
    }

    /**
     * @param excludeKeys 需要排除的key列表
     */
    public String select(List<String> excludeKeys) {
        final Weight weight = weightHolder.get(); // 后续实现保证不能改变w中任何数组的内容，否则线程不安全
        if (excludeKeys == null || excludeKeys.isEmpty()) {
            return select(weight.weightValueEnds, weight.weightKeys);
        }
        int[] weightValues = weight.weightValues.clone();
        for (int k = 0; k < weight.weightKeys.length; k++) {
            if (excludeKeys.contains(weight.weightKeys[k])) {
                weightValues[k] = 0;
            }
        }
        int[] weightValueEnds = genWeightValueEnds(weightValues);
        return select(weightValueEnds, weight.weightKeys);
    }

    private String select(int[] weightValueEnds, String[] weightKeys) {
        int sum = weightValueEnds[weightValueEnds.length - 1];
        if (sum == 0) {
            // 各个dskey的权重都为0,打印可读性更强的日志
            Weight weight = this.weightHolder.get();
            String dsKeys = Arrays.toString(weight.weightKeys);
            String weightValues = Arrays.toString(weight.weightValues);
            log.error("all of current dbkeys weight is 0, maybe db error happened, dbKeys:" + dsKeys + ", weight:" + weightValues);
            return null;
        }
        int rand = random.nextInt(sum);
        for (int i = 0; i < weightValueEnds.length; i++) {
            if (rand < weightValueEnds[i]) {
                return weightKeys[i];
            }
        }
        return null;
    }
}
